﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
#if !DOT_NET
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;
#endif
#if WINDOWS_PHONE
using StyleMVVM.Data.Collections;

#else
using System.Collections.Concurrent;
using StyleMVVM.Utilities;

#endif

namespace StyleMVVM.Suspension
{
	public sealed class SyncService : ISyncService
	{
		private static readonly string supplementalString = typeof(SyncService).FullName;
		private static readonly object lockObject = new object();
		private static volatile ISyncService instance;
		private readonly ConcurrentDictionary<string, SyncStrategy> strategies;
		private readonly ConcurrentDictionary<string, Tuple<WeakReference, SyncStrategy>> syncingObjects;
		private readonly ConcurrentDictionary<string, IDictionary<string, object>> syncInfo;
		private string syncfileName = "_syncFile.json";

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(SyncService)).
						 As(typeof(ISyncService)).
						 CoreExport(CoreExportStartOrder.Low).
						 AndSharedPermenantly();
		}

		public SyncService()
		{
			strategies = new ConcurrentDictionary<string, SyncStrategy>();
			syncingObjects = new ConcurrentDictionary<string, Tuple<WeakReference, SyncStrategy>>();
			syncInfo = new ConcurrentDictionary<string, IDictionary<string, object>>();
		}

		public static ISyncService Instance
		{
			get
			{
				if (instance == null)
				{
					lock (lockObject)
					{
						if (instance == null)
						{
							instance = Bootstrapper.Instance.Container.LocateByType(typeof(ISyncService)) as ISyncService;
						}
					}
				}

				return instance;
			}
		}

#if NETFX_CORE
		public IAsyncAction LoadStateFromDisk()
		{
			return InternalLoadStateFromDisk().AsAsyncAction();
		}

		public IAsyncAction Suspend()
		{
			return InternalSuspend().AsAsyncAction();
		}
#else
		public Task LoadStateFromDisk()
		{
			return InternalLoadStateFromDisk();
		}

		public Task Suspend()
		{
			return InternalSuspend();
		}
#endif

		internal async Task InternalLoadStateFromDisk()
		{
			try
			{
#if DOT_NET

#else
				StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(syncfileName);

				Dictionary<string, Dictionary<string, object>> tempDict;

				using (IInputStream inStream = await file.OpenSequentialReadAsync())
				{
					// Deserialize the Session State
					DataContractJsonSerializer serializer =
						new DataContractJsonSerializer(typeof(Dictionary<string, Dictionary<string, object>>), ReflectionService.KnownTypes);

					tempDict = (Dictionary<string, Dictionary<string, object>>)serializer.ReadObject(inStream.AsStreamForRead());
				}

				syncInfo.Clear();

				foreach (var syncName in tempDict.Keys)
				{
					syncInfo.TryAdd(syncName, tempDict[syncName]);
				}
#endif
			}
			catch (Exception exp)
			{
				Logger.Debug("Exception thrown while syncing from disk", supplementalString, exp);
			}
		}

		internal async Task InternalSuspend()
		{
			try
			{
				Dictionary<string, Dictionary<string, object>> allInfo =
					new Dictionary<string, Dictionary<string, object>>();

				foreach (KeyValuePair<string, IDictionary<string, object>> keyValuePair in syncInfo)
				{
					allInfo[keyValuePair.Key] = new Dictionary<string, object>(keyValuePair.Value);
				}

				foreach (string syncingObjectName in syncingObjects.Keys)
				{
					Tuple<WeakReference, SyncStrategy> reference;

					if (syncingObjects.TryGetValue(syncingObjectName, out reference))
					{
						object targetObject = reference;

						if (targetObject != null)
						{
							IDictionary<string, object> suspendInfo =
								reference.Item2.Suspend(targetObject);

							allInfo[syncingObjectName] = new Dictionary<string, object>(suspendInfo);
						}
					}
				}

				MemoryStream sessionData = new MemoryStream();
				DataContractJsonSerializer serializer =
					new DataContractJsonSerializer(typeof(Dictionary<string, Dictionary<string, object>>),
															 ReflectionService.KnownTypes);

				serializer.WriteObject(sessionData, allInfo);

#if DOT_NET
	// TODO
#else
				// Get an output stream for the SessionState file and write the state asynchronously
				StorageFile file =
					await ApplicationData.Current.LocalFolder.CreateFileAsync(syncfileName, CreationCollisionOption.ReplaceExisting);

				using (Stream fileStream = await file.OpenStreamForWriteAsync())
				{
					sessionData.Seek(0, SeekOrigin.Begin);
					await sessionData.CopyToAsync(fileStream);
					await fileStream.FlushAsync();
				}
#endif
			}
			catch (Exception exp)
			{
				Logger.Debug("Exception thrown while suspending from disk", supplementalString, exp);
			}
		}

		public void RegisterTypeForSync(Type syncType)
		{
			if (!strategies.ContainsKey(syncType.FullName))
			{
				SyncStrategy newStrategy = new SyncStrategy(syncType);

				strategies.TryAdd(syncType.FullName, newStrategy);
			}
		}

		public void RegisterInstanceForSync(object value)
		{
			SyncStrategy strategy;
			Type tType = value.GetType();

			if (!strategies.TryGetValue(tType.FullName, out strategy))
			{
				SyncStrategy newStrategy = new SyncStrategy(tType);

				strategies.TryAdd(tType.FullName, newStrategy);
			}

			syncingObjects[tType.FullName] =
				new Tuple<WeakReference, SyncStrategy>(new WeakReference(value), strategy);
		}

		public void RegisterInstanceForSyncWithContext(object value, string context)
		{
			SyncStrategy strategy;
			Type tType = value.GetType();

			if (!strategies.TryGetValue(tType.FullName, out strategy))
			{
				SyncStrategy newStrategy = new SyncStrategy(tType);

				strategies.TryAdd(tType.FullName, newStrategy);
			}

			syncingObjects[string.Concat(tType.FullName, "|", context)] =
				new Tuple<WeakReference, SyncStrategy>(new WeakReference(value), strategy);
		}

		public void Sync(object value)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}

			string syncName = value.GetType().FullName;

			SyncStrategy syncStrategy = GetSyncStrategy(value.GetType());

			if (syncStrategy != null)
			{
				try
				{
					IDictionary<string, object> suspendInfo =
						syncStrategy.Suspend(value);

					syncInfo.AddOrUpdate(syncName, suspendInfo, (x, y) => suspendInfo);
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while syncing object", supplementalString, exp);
				}
			}
		}



		public void SyncWithContext(object value, string context)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}

			string syncName = value.GetType().FullName;
			string contextName = string.Concat(syncName, "|", context);
			SyncStrategy syncStrategy = GetSyncStrategy(value.GetType());

			if (syncStrategy != null)
			{
				try
				{
					IDictionary<string, object> suspendInfo =
						syncStrategy.Suspend(value);

					syncInfo.AddOrUpdate(contextName, suspendInfo, (x, y) => suspendInfo);
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while syncing object", supplementalString, exp);
				}
			}
		}

		public void Resume(object value)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}

			string syncName = value.GetType().FullName;
			IDictionary<string, object> info;

			if (syncInfo.TryGetValue(syncName, out info))
			{
				SyncStrategy syncStrategy = GetSyncStrategy(value.GetType());

				if(syncStrategy != null)
				{
					try
					{
						syncStrategy.Resume(value, info);
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while syncing object", supplementalString, exp);
					}
				}
			}
		}

		public void ResumeWithContext(object value, string context)
		{
			if (value == null)
			{
				throw new ArgumentNullException("value");
			}

			string syncName = value.GetType().FullName;
			string contextName = string.Concat(syncName, "|", context);

			IDictionary<string, object> info;

			if (syncInfo.TryGetValue(contextName, out info))
			{
				SyncStrategy syncStrategy = GetSyncStrategy(value.GetType());

				if (syncStrategy != null)
				{
					try
					{
						syncStrategy.Resume(value, info);
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while syncing object", supplementalString, exp);
					}
				}
			}
		}

		private SyncStrategy GetSyncStrategy(Type syncType)
		{
			string syncName = syncType.FullName;
			SyncStrategy syncStrategy;

			if (!strategies.TryGetValue(syncName, out syncStrategy))
			{
				foreach (Attribute customAttribute in syncType.GetTypeInfo().GetCustomAttributes())
				{
					if (customAttribute is ISyncableAttribute)
					{
						syncStrategy = new SyncStrategy(syncType);

						break;
					}
				}

				strategies.AddOrUpdate(syncName, x => syncStrategy, (x, y) => syncStrategy);
			}

			return syncStrategy;
		}
	}
}